package com.lcy.struct.bTreePlus.disk.recovery.redo;

import com.lcy.struct.bTreePlus.disk.TestTree;
import com.lcy.struct.bTreePlus.disk.constants.BPlusPublicConstants;
import com.lcy.struct.bTreePlus.disk.file.FileUtil;
import com.lcy.struct.bTreePlus.disk.log.LogUtils;
import com.lcy.struct.bTreePlus.disk.recovery.RecoveryCenter;
import com.lcy.struct.bTreePlus.entity.RedoInfo;
import com.lcy.struct.bTreePlus.utils.BplusUtils;
import lombok.SneakyThrows;

import java.io.*;
import java.nio.MappedByteBuffer;
import java.nio.channels.FileChannel;
import java.util.List;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantReadWriteLock;

/**
 * @author lucongyang
 * @date Created in  10:00
 * @description
 */
public class RedoCenter {
    private static final String REDO_LOG = TestTree.root_url + "redo.log";
    private static ReentrantReadWriteLock redoEndPositionLOCK = new ReentrantReadWriteLock();
    public static final Lock REDO_END_POSITION_WRITE_LOCK = redoEndPositionLOCK.writeLock();
    public static final Lock REDO_END_POSITION_READ_LOCK = redoEndPositionLOCK.readLock();
    private Long redoEndPosition;

    private RandomAccessFile redoFile;

    private FileChannel redoChannel;

    /**
     * 写redo
     *
     * @param transactionId 事务id
     * @param redoInfo      redo信息
     */
    public void writeIntoRedo(long transactionId, RedoInfo redoInfo) {
        byte[] bytes = null;
        try {
            bytes = RecoveryCenter.getInstance().objectMapper.writeValueAsBytes(redoInfo);
        } catch (Exception e) {
            LogUtils.log("事务id={}，序列化失败，redoInfo={},错误={}", transactionId, redoInfo, e.getMessage());
            throw new RuntimeException("事务id=" + transactionId + "，序列化失败", e);
        }
        try {
            WriteTransactionIntoRedo.getInstance().writeIntoRedo(bytes, transactionId);
        } catch (Exception e) {
            LogUtils.log("事务id={}，写redo失败，错误={}", transactionId, redoInfo, e.getMessage());
            throw new RuntimeException("事务id=" + transactionId + "，写redo失败", e);
        }
    }

    /**
     * 获取redo列表
     */
    public List<RedoInfo> getTransactionRedoList() {
        try {
            return DoRedo.getInstance().getTransactionRedoList();
        } catch (Exception e) {
            LogUtils.log("执行redo失败错误={}", e.getMessage());
            throw new RuntimeException("执行redo失败错误", e);
        }
    }

    /**
     * 做redo任务
     *
     * @param formedList redo任务列表
     */
    public void doRedo(List<RedoInfo> formedList) {
        Long[] commitTransactionIds = DoRedo.getInstance().doRedo(formedList);
        // 提交
        CommitRedo.getInstance().commitRedo(commitTransactionIds);
    }

    /**
     * 清空redo日志
     *
     * @param force 是否彻底清空，删文件再恢复
     */
    @SneakyThrows
    public void clearRedo(boolean force) {
        REDO_END_POSITION_WRITE_LOCK.lock();
        try {
            long size = getRedoEndPosition() - BPlusPublicConstants.REDO_END_POSITION - BPlusPublicConstants.HEAD_SIZE;
            modifyRedoEndPosition(BPlusPublicConstants.REDO_END_POSITION + BPlusPublicConstants.HEAD_SIZE);
            if (force) {
                int n = 1000000;
                for (int i = 0; i * n < size; i++) {
                    byte[] bytes = new byte[(int) Math.min(size - i * n, n)];
                    BplusUtils.initByteArray(bytes);
                    FileUtil.getInstance().straightRewrite(getRedoChannel(), i * n + BPlusPublicConstants.REDO_END_POSITION + BPlusPublicConstants.HEAD_SIZE,
                            bytes.length, true, bytes);
                }
            }
        } finally {
            REDO_END_POSITION_WRITE_LOCK.unlock();
        }
    }

    /**
     * 写wndPosition
     */
    void allocate(long size) {
        REDO_END_POSITION_WRITE_LOCK.lock();
        try {
            long exceptEndPosition = getRedoEndPosition() + size;
            modifyRedoEndPosition(exceptEndPosition);
            this.redoEndPosition = exceptEndPosition;
        } finally {
            REDO_END_POSITION_WRITE_LOCK.unlock();
        }
    }

    /**
     * 获取通道
     */
    FileChannel getRedoChannel() {
        if (this.redoChannel != null) {
            return this.redoChannel;
        }
        RandomAccessFile randomAccessFile = getRedoAccessFile();
        this.redoChannel = randomAccessFile.getChannel();
        return this.redoChannel;
    }

    long getRedoEndPosition() {
        REDO_END_POSITION_READ_LOCK.lock();
        try {
            if (this.redoEndPosition != null) {
                return this.redoEndPosition;
            }
            byte[] endPositionByte = FileUtil.getInstance().read(BPlusPublicConstants.REDO_END_POSITION, BPlusPublicConstants.HEAD_SIZE, getRedoChannel());
            String s = new String(endPositionByte).trim();
            this.redoEndPosition = s.isEmpty() ? BPlusPublicConstants.REDO_END_POSITION + BPlusPublicConstants.HEAD_SIZE : Long.parseLong(s);
            return this.redoEndPosition;
        } finally {
            REDO_END_POSITION_READ_LOCK.unlock();
        }
    }

    public static RedoCenter getInstance() {
        return Singleton.INSTANCE;
    }

    private static class Singleton {
        private static final RedoCenter INSTANCE = new RedoCenter();
    }

    private RedoCenter() {
    }

    /**
     * 修改指针
     */
    @SneakyThrows
    private void modifyRedoEndPosition(long exceptEndPosition) {
        REDO_END_POSITION_WRITE_LOCK.lock();
        try {
            FileUtil.getInstance().straightRewrite(getRedoChannel(), BPlusPublicConstants.REDO_END_POSITION,
                    BPlusPublicConstants.HEAD_SIZE, true, (exceptEndPosition + "").getBytes());
            this.redoEndPosition = exceptEndPosition;
        } finally {
            REDO_END_POSITION_WRITE_LOCK.unlock();
        }
    }

    private RandomAccessFile getRedoAccessFile() {
        if (redoFile != null) {
            return redoFile;
        }
        try {
            redoFile = new RandomAccessFile(REDO_LOG, "rw");
        } catch (FileNotFoundException e) {
            LogUtils.log("redo文件：{}不存在，正在初始化", REDO_LOG);
            createRedo();
            return getRedoAccessFile();
        }
        return redoFile;
    }

    /**
     * 初始化redo
     * 会修改endPosition值
     */
    private void createRedo() {
        File file = new File(REDO_LOG);
        REDO_END_POSITION_WRITE_LOCK.lock();
        try {
            this.redoEndPosition = BPlusPublicConstants.REDO_END_POSITION + BPlusPublicConstants.HEAD_SIZE;
        } finally {
            REDO_END_POSITION_WRITE_LOCK.unlock();
        }
    }
}
